package lldp

import (
	"bytes"
	"fmt"
	"log/slog"
	"os"
	"path"
	"path/filepath"
	"syscall"
	"time"

	mlldp "gitee.com/rogax/kagent/pkg/lldp/lldpd"
	"gitee.com/rogax/kagent/pkg/utils"
	"golang.org/x/sys/unix"
)

type LLDPDServer struct {
	// MachineUUID is the unique UUID for this machine, usually the board serial.
	MachineUUID string `yaml:"machineuuid"`
	// Timestamp is the the timestamp of installer config creation.
	Timestamp string `yaml:"timestamp"`
}

const (
	debugFs         = "/sys/kernel/debug"
	machineUUIDFile = "/sys/class/dmi/id/product_uuid"
	// installYaml = "/etc/metal/install.yaml"
)

func NewLLDPDServer() *LLDPDServer {
	b, err := os.ReadFile(machineUUIDFile)
	if err != nil {
		log.Warn("lldpd", "unable to open config", err)
	}
	uuid := string(bytes.TrimSpace(b))
	if len(uuid) == 0 {
		if h, err := os.Hostname(); err != nil {
			log.Warn("lldpd", "unable to get hostname", err)
			uuid = "unkown"
			// os.Exit(1)
		} else {
			uuid = h
		}
	}

	return &LLDPDServer{
		MachineUUID: uuid,
		Timestamp:   time.Now().Format(time.RFC3339),
	}
}

// Starts lldp on every ethernet nic that is up
func (l *LLDPDServer) Run() {
	// jsonHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})
	// log := slog.New(jsonHandler)
	// log.Info("lldpd", "version", v.V)

	stopFirmwareLLDP(log)

	interfaces, err := utils.GetInterfaces()
	if err != nil {
		log.Error("unable to get interfaces", "error", err)
	}

	if len(interfaces) < 2 {
		slog.Info("exiting, because not enough interfaces are up - we need at least two")
		return
	}
	log.Info("will start lldp on interfaces", "interfaces", interfaces)

	desc := fmt.Sprintf("provisioned since %s", l.Timestamp)
	for _, iface := range interfaces {
		lldpd, err := mlldp.NewDaemon(log, l.MachineUUID, desc, iface, 2*time.Second)
		if err != nil {
			log.Error("could not start lldp for interface", "interface", iface)
			os.Exit(-1)
		}
		lldpd.Start()
	}
	// select {}
}

func unmountDebugFs(log *slog.Logger) {
	log.Info("unmounting debugfs")
	err := syscall.Unmount(debugFs, syscall.MNT_FORCE)
	if err != nil {
		log.Error("unable to unmount debugfs", "error", err)
	}
}

// stopFirmwareLLDP stop Firmeware LLDP not persistent over reboots, only during runtime.
// mount -t debugfs none /sys/kernel/debug
// echo lldp stop > /sys/kernel/debug/i40e/0000:01:00.2/command
// where <0000:01:00.2> is the pci address of the ethernet nic, this can be inspected by lspci,
// or a loop over all directories in /sys/kernel/debug/i40e/*/command
func stopFirmwareLLDP(log *slog.Logger) {
	var stat syscall.Statfs_t
	err := syscall.Statfs(debugFs, &stat)
	if err != nil {
		log.Error("could not check whether debugfs is mounted", "error", err)
		return
	}

	if stat.Type != unix.DEBUGFS_MAGIC {
		log.Info("mounting debugfs")
		err := syscall.Mount("debugfs", debugFs, "debugfs", 0, "")
		if err != nil {
			log.Error("mounting debugfs failed", "error", err)
			return
		}
		defer unmountDebugFs(log)
	}

	var buggyIntelNicDriverNames = []string{"i40e"}
	for _, driver := range buggyIntelNicDriverNames {
		debugFSPath := path.Join(debugFs, driver)
		log.Info("check whether lldp needs to be deactivated", "path", debugFSPath)

		if _, err := os.Stat(debugFSPath); os.IsNotExist(err) {
			log.Info("nothing to do here, because directory for driver does not exist", "path", debugFSPath)
			continue
		}

		err := filepath.Walk(debugFSPath, func(path string, info os.FileInfo, err error) error {
			if err != nil {
				log.Warn("opening/reading debugfs failed", "path", path, "error", err)
				return err
			}
			if !info.IsDir() && info.Name() == "command" {
				log.Info("execute echo lldp stop > ", "path", path)
				stopCommand := []byte("lldp stop")
				err := os.WriteFile(path, stopCommand, os.ModePerm)
				if err != nil {
					log.Error("stop lldp > command failed", "path", path, "error", err)
				}
			}
			return nil
		})
		if err != nil {
			log.Error("unable to walk through debugfs", "path", debugFSPath, "error", err)
		}
	}
}
